#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         PythonEntitySource.py
#
# Copyright:    Copyright (c) 2006, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  An 'EntitySource' that provides 'Country' entities.
#
# See Also:     Entity, *EntityChooser, *EntitySource
#####################################################################

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager
 
#####################################################################
## Python Library Imports
# (none)

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Entity import Entity
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.EventDefinition import *
from Products.KnowledgeEngine.PermissionDefinition import *
from Products.KnowledgeEngine.EntitySource import EntitySource, EntitySourceInfo

#####################################################################
## CMF Library Imports
from Products.CMFCore import permissions
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore.utils import getToolByName

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from Globals import Persistent
from App.ImageFile import ImageFile
from Products.ZSQLMethods.SQL import SQL
from DateTime import DateTime
from AccessControl import ClassSecurityInfo

#####################################################################
## Python Library Imports
import os
import base64
import binascii

def parseRow( item ):

    # Decode the vocab items
    item_len = len(item)
    
    if item_len == 2:
        # Two-Tuple - Just treat as key-value pair
        identity, title = item
        data = {}
    elif item_len == 3:
        # Three-Tuple - Treat as key-value-data triplet
        identity, title, data = item
    else:
        raise "Unknown Entity Format: %s" % `item`
        
    
    if not type(identity) is str: 
        raise "Invalid identity type: %s" % `identity`
       
    # Wrap in an entity
    entity = Entity( identity, title, data )
    
    return entity


#####################################################################
## PythonEntitySourceInfo Class Definition
class PythonEntitySourceInfo(EntitySourceInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return 'PythonEntitySource'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "PythonEntitySource"

    #---------------------------------------------------------------#

    def getDescription(self):
        """
            ABSTRACT: Return a friendly description on the purpose
            of this class.  This description is often used in the
            Property Editor for instances of this class
        """

        return "Defines a source based on a python script"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.
        """

        return "esPython"

    #---------------------------------------------------------------#

    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """

        return ImageFile('skins/PythonEntitySource_icon.gif', globals())

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.

            Override an return true if the instance is to be
            a container for other instances.

            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        return false

    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in
            the specified container (containerInstance)

            NOTE: This is from this class' instance perspective.
        """

        #PythonEntitySources don't contain anything
        return false

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """

        #PythonEntitySources don't contain anything
        return false

    #---------------------------------------------------------------#

    def isPortalObject(self):
        """
            Returns if the object can be placed directly in 
            a Plone Portal (rather than like most objects
            that must exist within a GenericObject container
            object.
            
            eg: Models, EntitySources are Portal Objects.
        """
        
        return true

    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific properties using the PropertyReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """
        #initialise the super-class properties for reflection
        EntitySourceInfo.onConfigureProperties(self)
        
        self.addPropertyDefinition(PropertyDefinition(
            identity="scriptName", 
            displayName="Script Name", 
            description="The name of a python script in context to execute. Accepts arguments 'user' and 'filterBy'", 
            defaultValue="", 
            propertyType=pt_STRING, 
            propertyFormat=pf_SKINCHOOSER, 
            objectType="vocabScript",
            propertyStructure=ps_ATOMIC,  
        ))
        

    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific events using the EventReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class events for reflection
        EntitySourceInfo.onConfigureEvents(self)

    #---------------------------------------------------------------#

    def onConfigurePermissions(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific permission definitions.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class permissions for reflection
        EntitySourceInfo.onConfigurePermissions(self)

    #---------------------------------------------------------------#

#####################################################################
## PythonEntitySource Class Definition
class PythonEntitySource(EntitySource):
    """
        ISO Country Entity Source
    """

    security = ClassSecurityInfo()

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = PythonEntitySourceInfo()

    #---------------------------------------------------------------#
    # Default Attribute Values
    meta_type = 'PythonEntitySource'


    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the object"

        EntitySource.__init__(self, id, identity)

        #perform onAfterInstanciation
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation for the container
        EntitySource.onAfterInstanciation(self)

    #---------------------------------------------------------------#

    def getNrEntities(self, user = None, filterBy = {}):
        """
            Returns the number of entities available from this
            PythonEntitySource for the specified user.
            
            The user (optional) is provided to enable PythonEntitySources to filter by user.
        """
 
        # Remove authenticatedUser from filterBy if it's present, as it has
        filterBy.pop('authenticatedUser', None)
       
        return len( self.getEntities() )

    #---------------------------------------------------------------#

    def getScriptName(self):
        """ Returns the name of the python script to execute """
        
        return getattr(self, 'scriptName', None)

    #---------------------------------------------------------------#

    def getEntities(self, user = None, filterBy = {}):
        """
            Returns the entities from this entity source,
            ordered by entity identity.
        """

        scriptName = self.getScriptName()
        script     = getattr(self, scriptName, None)
        if script:        
            
            try: 
                results = script(user=user, filterBy=filterBy)
            except TypeError:
                results = script()
            
            parsed = []
            for item in results:
                parsed.append( parseRow( item ) )
                
                
            return parsed

    #---------------------------------------------------------------#

    def getEntitiesByBatch(self, batchNr, batchSize = 10, user = None, filterBy = {}):
        """
            Returns a batch of entities as specified.
            
            NOTE 1: The first available batch is 0
            
            NOTE 2: The entities are returned in order of their name
        """
                
        return self.getEntities(user=user, filterBy=filterBy)[batchNr * batchSize:batchSize]

    #---------------------------------------------------------------#

    def getEntity(self, entityIdentity, user = None):
        """
            Returns the entity with the specified identity
            for the provided user
        """
          
        scriptName = self.getScriptName()
        script     = getattr(self, scriptName, None)
        if script:      
            
            try:  
                results = script(entityIdentity=entityIdentity, user=user)
            except TypeError:
                try:
                    results = script(user=user)
                except TypeError:
                    results = script()
                    
            
            if type( results ) is list: # Script didn't take the hint, returned us a full list anyway.                
                for item in results:
                    parsed = parseRow( item )
                    if parsed.getIdentity() == entityIdentity:
                        return parsed
            else:
                return parseRow( results )
                
                

        
    #---------------------------------------------------------------#

    def getEntityNameSingular(self):
        """
            ABSTRACT: Return the name of a single entity provided 
            by the Entity Source
        """
        
        return "Entity"
        
    #---------------------------------------------------------------#

    def getEntityNamePlural(self):
        """
            ABSTRACT: Return the name of a collection of entities provided 
            by the Entity Source
        """
        
        return "Entities"

    #---------------------------------------------------------------#

    def hasDefaultView(self):
        """
            PLONE Support:

            Returns if the object has a default view.  By setting this
            to true, a view tab will be added to the object.

            If this is false, attempting to visit the / (default view)
            for a component will redirect the client to the
            next available parent that has a default view.
        """

        return false
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

#####################################################################
## CMF Required Class Initialisation

def addPythonEntitySource(self, id, REQUEST=None):
    """
        The Zope/CMF factory method to create an instance
    """

    #create a KnowledgeEngine identity for the new model
    identity = generateIdentity(PythonEntitySource.identity_type)

    #create an instance
    object = PythonEntitySource(id, identity)

    #add the object to the specified container (called self)
    self._setObject(id, object)

    if REQUEST is not None:
        REQUEST['RESPONSE'].redirect(object.absolute_url() + '/manage_main' )


#the CMF/Plone factory type information - used to create objects!)
factoryTypeInformation = {
                               'id'             : 'PythonEntitySource'
                             , 'meta_type'      : 'PythonEntitySource'
                             , 'description'    : "A KnowledgeEngine PythonEntitySource"
                             , 'icon'           : 'PythonEntitySource_icon.gif'
                             , 'product'        : 'KnowledgeEngine'
                             , 'factory'        : 'addPythonEntitySource'
                             , 'filter_content_types' : 0
                             , 'global_allow'   : 0
                             , 'immediate_view' : 'view'
                             , 'actions'        :
                                ( { 'id'            : 'view'
                                  , 'name'          : 'View'
                                  , 'action'        : 'string:${folder_url}/view'
                                  , 'permissions'   : (permissions.View,)
                                  , 'category'      : 'folder'
                                  },
                                {
                                    'id'            : 'edit',
                                    'name'          : 'Edit',
                                    'action'        : 'string:${folder_url}/edit_form',
                                    'permissions'   : ("Modify portal content",),
                                    'category'      : 'object',
                                },                                     

                                )
                         }

#####################################################################
## Class Initialisation

# Register Class Information with the KnowledgeEngine
registerPortalObjectClass(PythonEntitySource, addPythonEntitySource, factoryTypeInformation)

#####################################################################
